﻿// (c) Copyright slimCODE Software Inc. - www.slimcode.com
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.

using System;
using System.Security.Cryptography;

namespace SlimCode.Utils
{
  public abstract class HMAC : KeyedHashAlgorithm
  {
    private static RandomNumberGenerator mg_random; // = null

    internal static byte[] GenerateRandom( int count )
    {
      if( mg_random == null )
      {
        mg_random = RandomNumberGenerator.Create();
      }

      byte[] buffer = new byte[ count ];

      mg_random.GetBytes( buffer );

      return buffer;
    }

    // m_inner = PaddedKey ^ {0x36...} 
    // m_outer = PaddedKey ^ {0x5C...}
    private byte[] m_inner;
    private byte[] m_outer;

    private bool m_hashing = false;

    #region BlockSizeValue PROPERTY

    private int m_blockSizeValue = 64;

    protected int BlockSizeValue
    {
      get { return m_blockSizeValue; }
      set { m_blockSizeValue = value; }
    }

    #endregion

    #region Key PROPERTY

    public override byte[] Key
    {
      get { return ( byte[] )m_key.Clone(); }
      set
      {
        if( m_hashing )
          throw new CryptographicException( "Cannot set key after hashing started." );

        this.InitializeKey( value );
      }
    }

    #endregion

    #region HashName PROPERTY

    internal string m_hashName;

    internal HashAlgorithm m_hash1;
    internal HashAlgorithm m_hash2;

    public string HashName
    {
      get { return m_hashName; }
      set
      {
        if( m_hashing )
          throw new CryptographicException( "Cannot set name after hashing started." );

        m_hashName = value;

        m_hash1 = HashAlgorithm.Create( m_hashName );
        m_hash2 = HashAlgorithm.Create( m_hashName );
      }
    }

    #endregion

    new public static HMAC Create()
    {
      return HMAC.Create( "System.Security.Cryptography.HMAC" );
    }

    new public static HMAC Create( string algorithmName )
    {
      return ( HMAC )CryptoConfig.CreateFromName( algorithmName );
    }

    public override void Initialize()
    {
      m_hash1.Initialize();
      m_hash2.Initialize();
      m_hashing = false;
    }

    protected override void HashCore( byte[] rgb, int ib, int cb )
    {
      if( m_hashing == false )
      {
        m_hash1.TransformBlock( m_inner, 0, m_inner.Length, m_inner, 0 );
        m_hashing = true;
      }

      m_hash1.TransformBlock( rgb, ib, cb, rgb, ib );
    }

    protected override byte[] HashFinal()
    {
      if( m_hashing == false )
      {
        m_hash1.TransformBlock( m_inner, 0, m_inner.Length, m_inner, 0 );
        m_hashing = true;
      }

      // Finalize the original hash 
      m_hash1.TransformFinalBlock( new byte[ 0 ], 0, 0 );
      byte[] hashValue1 = m_hash1.Hash; // Cloning...

      // Write the outer array 
      m_hash2.TransformBlock( m_outer, 0, m_outer.Length, m_outer, 0 );

      // Write the inner hash and finalize the hash 
      m_hash2.TransformBlock( hashValue1, 0, hashValue1.Length, hashValue1, 0 );

      m_hashing = false;

      m_hash2.TransformFinalBlock( new byte[ 0 ], 0, 0 );

      return m_hash2.Hash; // Cloning...
    }

    internal void InitializeKey( byte[] key )
    {
      m_inner = null;
      m_outer = null;

      if( key.Length > this.BlockSizeValue )
      {
        m_key = m_hash1.ComputeHash( key );
      }
      else
      {
        m_key = ( byte[] )key.Clone();
      }

      this.UpdateIOPadBuffers();
    }

    private void UpdateIOPadBuffers()
    {
      if( m_inner == null )
      {
        m_inner = new byte[ BlockSizeValue ];
      }

      if( m_outer == null )
      {
        m_outer = new byte[ BlockSizeValue ];
      }

      for( int i = 0; i < this.BlockSizeValue; i++ )
      {
        m_inner[ i ] = 0x36;
        m_outer[ i ] = 0x5C;
      }

      for( int i = 0; i < m_key.Length; i++ )
      {
        m_inner[ i ] ^= m_key[ i ];
        m_outer[ i ] ^= m_key[ i ];
      }
    }

    #region IDisposable IMPLEMENTATION

    protected override void Dispose( bool disposing )
    {
      if( disposing )
      {
        if( m_hash1 != null )
          m_hash1.Clear();

        if( m_hash2 != null )
          m_hash2.Clear();

        if( m_inner != null )
          Array.Clear( m_inner, 0, m_inner.Length );

        if( m_outer != null )
          Array.Clear( m_outer, 0, m_outer.Length );
      }

      base.Dispose( disposing );
    }

    #endregion
  }
}
